package sudoku;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Group;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;


/**
 * Class creates JPanel with image of current game board. it also provides basic
 * functionality to alow player to play sudoku.
 * @author Taras Basiuk
 * @version 0.1
 */
public class ClassicCanvas extends JPanel{
    
    /** Grid reference, where all the data come from and goes to. */
    private ClassicGrid grid;
    
    /** Level of linked classic sudoku grid. */
    private int level;
    
    /** Number of cell neighbors in the grid. */
    private int neighbors;
    
    /** 
     * Scale factor. Used to enlarge or shrink things. default value is 1. 
     * Range should be from 0.5 to 2.
     */
    private double scale;
    
    /** Length of cell side on the screen. Default value is 30. */
    private int cellSize;
    
    /** Horizontal length of sudoku canvas.*/
    private int width;
    
    /** Vertical length of sudoku canvas.*/
    private int height;

    /** Image of one tile for background. */
    private BufferedImage backgroundImg; 
    
    /** Left top corner x coordinate of rectangle pointed by mouse. */
    private Integer xRectangle;
    
    /** Left top corner x coordinate of rectangle pointed by mouse. */
    private Integer yRectangle;
    
    /** Number of cell pointed by mouse. */
    private Integer pointedCellNum;
    
    /** Chosen by user value to be put in chosen cell. */
    private Integer choise;
    
    /** Window with buttons for user to choose value to put in chosen cell.*/
    private JFrame choiseFrame;
    
    /** Reference to main game frame. */
    private MainFrame frame;

    /** 
     * Constructor links this grid to given grid. Sets scale to 1. 
     * Reads default backgroungImg image tile. Sets cellSize to be 30.
     * Calculates h and w. Sets mouse listeners.
     */
    public ClassicCanvas(MainFrame fr){
        super();

        this.frame = fr;
        grid = fr.getGridCopy();        
        scale = 1;
        cellSize = 30;
        level = grid.getLevel();
        neighbors = level*level;
        width = cellSize * neighbors;
        height = cellSize * neighbors;

        // Trying to read image tile for background.
        try {
            backgroundImg = ImageIO.read(new File("src/sudoku/resourses/background.jpg"));
        } catch (IOException ex) {
            System.out.println("No background image found.");
        }
        
        setPreferredSize(new Dimension(width+1,height+1));
        
        this.addMouseMotionListener(new MouseMotionListener(){

            @Override
            public void mouseDragged(MouseEvent e) {
                mouseMovement(e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                mouseMovement(e);
            }
        });
        
        this.addMouseListener(new MouseListener(){

            @Override
            public void mouseClicked(MouseEvent e) {
                frame.setEnabled(false);
                mouseClick(e);
            }

            @Override
            public void mousePressed(MouseEvent e) {               
            }

            @Override
            public void mouseReleased(MouseEvent e) {                
            }

            @Override
            public void mouseEntered(MouseEvent e) {                
            }

            @Override
            public void mouseExited(MouseEvent e) {                
            }
        });
    }
    
    /** 
     * This methods sets coordinates to highlight square under mouse pointer,
     * and determines number of cell on the grid being pointed.
     */
    private void mouseMovement(MouseEvent e){
        
        int x = e.getX();
        int y = e.getY();
        
        xRectangle = (x/cellSize)*cellSize;
        yRectangle = (y/cellSize)*cellSize;  
        
        pointedCellNum = (y/cellSize)*neighbors + x/cellSize;
        
        repaint();
    }
    
    /** This method determines what happens when user clicks somewhere on canvas. */
    private void mouseClick(MouseEvent e){

        if (!grid.getCopyCell(pointedCellNum).getAccess()) {
            frame.setEnabled(true);
            return; // Chosen cell cannot be edited by user
        } else if (grid.getCopyCell(pointedCellNum).getValue() == 0) {
            // Chosen cell is empty, create custom dialog frame
            choiseFrame = new JFrame("Enter value");
            choiseFrame.addWindowListener(new WindowListener() {
                @Override
                public void windowOpened(WindowEvent e) {                    
                }
                @Override
                public void windowClosing(WindowEvent e) {
                    // Need to reanable main frame if user closes this frame before chosing value.
                    frame.setEnabled(true);
                }
                @Override
                public void windowClosed(WindowEvent e) {                    
                }
                @Override
                public void windowIconified(WindowEvent e) {                    
                }
                @Override
                public void windowDeiconified(WindowEvent e) {                    
                }
                @Override
                public void windowActivated(WindowEvent e) {                    
                }
                @Override
                public void windowDeactivated(WindowEvent e) {                    
                }
            });            
            
            JPanel panel = new JPanel();
            GroupLayout layout = new GroupLayout(panel);
            // Array with button references
            JButton[] buttons = new JButton[neighbors];
            
            for (int i = 0; i < neighbors; i++) {
                final JButton button = new JButton(String.valueOf(i + 1));
                button.setMargin(new Insets(0,0,0,0));
                button.setPreferredSize(new Dimension(cellSize, cellSize));
                buttons[i] = button;
                button.addActionListener(new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {  
                        // When any button gets pressed ...
                        choise = Integer.parseInt(button.getText());
                        choiseFrame.setVisible(false);
                        choiseFrame.dispose();
                        boolean validTurn = frame.insertToGrid(pointedCellNum, choise, true);
                        if (validTurn){
                            grid = frame.getGridCopy();
                            repaint();
                            if (grid.getUnsolvedCells()==0) JOptionPane.showMessageDialog(frame, "Congratulations, you've won !!!");
                        } else {
                            JOptionPane.showMessageDialog(frame, 
                                    "Invalid input. You can't insert this value in chosen cell.");
                        }
                        frame.setEnabled(true);                         
                    }
                });
            }

            panel.setLayout(layout);
            // Horizontal layout setup. Fill frame with buttons.
            Group horizontal = layout.createSequentialGroup();
            for (int i = 0; i < level; i++) {
                Group subGroup = layout.createParallelGroup();
                for (int j = 0; j < level; j++) {
                    subGroup.addComponent(buttons[j * level + i], GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE,GroupLayout. PREFERRED_SIZE);
                }
                horizontal.addGroup(subGroup);
            }
            layout.setHorizontalGroup(horizontal);
            // Vertical Layout setup
            Group vertical = layout.createSequentialGroup();
            for (int i = 0; i < level; i++) {
                Group subGroup = layout.createParallelGroup();
                for (int j = 0; j < level; j++) {
                    subGroup.addComponent(buttons[i * level + j], GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE,GroupLayout. PREFERRED_SIZE);
                }
                vertical.addGroup(subGroup);
            }
            layout.setVerticalGroup(vertical);

            choiseFrame.add(panel);
            // Place dialog frame close to location where mouse was clicked
            choiseFrame.setLocation(e.getXOnScreen() - (level / 2) * cellSize, 
                    e.getYOnScreen() - (level / 2) * cellSize);
            choiseFrame.pack();
            choiseFrame.setResizable(false);
            choiseFrame.setVisible(true);
        } else {  
            // Chosen cell not empty but editable, confirm erase.
            int responce = JOptionPane.showConfirmDialog(frame, 
                    "Erase value?", "Confirm erase", JOptionPane.YES_NO_OPTION);
            if (responce == JOptionPane.YES_OPTION){                
                frame.eraseFromGrid(pointedCellNum, true);
                grid = frame.getGridCopy();
                repaint();
            }
            frame.setEnabled(true);
        }
    }
    
    /** 
     * This method capable of setting scale factor to one of five values:
     * {0.5; 0.75; 1; 1.5; 2}.
     */
    public void setScale(double newScale){   
        
        if (newScale <= 0.5){
            scale = 0.5;
        } else if (newScale > 0.5 && newScale <= 0.75){
            scale = 0.75;
        } else if (newScale > 0.75 && newScale <= 1){
            scale = 1;
        } else if (newScale > 1 && newScale <= 1.5){
            scale = 1.5;
        } else if (newScale > 1.5){
            scale = 2;
        }
        
        cellSize = (int) (cellSize*scale);
        width = cellSize * neighbors;
        height = cellSize * neighbors;
        repaint();
    };

    /** This method makes all the paint work ...*/
    @Override
    public void paintComponent(Graphics g)
        {
           super.paintComponent(g);

           // trying to paint background
           try {
                int x, y;
		int w, h;

		Rectangle clip = g.getClipBounds();

		w = backgroundImg.getHeight(this);
		h = backgroundImg.getWidth(this);

		if(w > 0 && h > 0)
		{
			for(x = clip.x+1; x < (clip.x + clip.width) ; x += w)
			{
				for(y = clip.y+1; y < (clip.y + clip.height) ; y += h)
				{
					g.drawImage(backgroundImg,x,y,this);					
				}
			}
		}
           } catch (Exception e){
           }

           // Draw lines.
           for (int i =0; i<neighbors+1; i++){
           g.drawLine(i*width/neighbors, 0, i*width/neighbors, height);
           g.drawLine(0, i*height/neighbors, width, i*height/neighbors);
           }
           for (int i =0; i<level+1; i++){
           g.drawLine(i*width/level-1, 0, i*width/level-1, height);
           g.drawLine(0, i*height/level-1, width, i*height/level-1);
           g.drawLine(i*width/level+1, 0, i*width/level+1, height);
           g.drawLine(0, i*height/level+1, width, i*height/level+1);
           }

            // Inserts cell values in the grid
            if (grid != null) {
                grid = frame.getGridCopy();
                Font font = new Font(null, 0, (int) (cellSize * 0.65));
                g.setFont(font);
                TreeMap<Integer, ClassicCell> map = grid.getCopyGridMap();

                for (int i = 0; i < neighbors * neighbors; i++) {

                    int value = map.get(i).getValue();
                    if (value != 0) {
                        boolean access = grid.getAccess().get(i);
                        char[] data = new char[2];
                        data[0] = Character.forDigit(value, 10);
                        if (value > 9) {
                            data[0] = Character.forDigit(value / 10, 10);
                            data[1] = Character.forDigit(value % 10, 10);
                        }

                        if (access) {
                            g.setColor(Color.BLUE);
                        } else {
                            g.setColor(Color.BLACK);
                        }

                        int length;
                        int horizontalShift;
                        if (value < 10) {
                            length = 1;
                            horizontalShift = (width / (neighbors * 3));
                        } else {
                            length = 2;
                            horizontalShift = (int) (4 * scale);
                        }
                        g.drawChars(data, 0, length, (i % neighbors) * (width / neighbors) + horizontalShift,
                                (i / neighbors) * (height / neighbors) + (height / ((int) (neighbors * (1.3)))));
                    }
                }
            }
            // Draw rectangle pointed by mouse.
            if (xRectangle != null && pointedCellNum<grid.getNumCells()) {
                if (grid.getCopyCell(pointedCellNum).getAccess()) {
                    if (grid.getCopyCell(pointedCellNum).getValue() != 0) {
                        g.setColor(Color.RED);
                    } else {
                        g.setColor(Color.BLUE);
                    }
                    g.drawRect(xRectangle + 3, yRectangle + 3, cellSize - 6, cellSize - 6);
                }
            }
        }    
}
